package longrise.phone.com.bjjt_henan.Utils.icomet;

import android.net.wifi.WifiConfiguration.Status;

import java.net.HttpURLConnection;
import java.net.URL;
import java.util.Timer;
import java.util.TimerTask;

import longrise.phone.com.bjjt_henan.Utils.icomet.message.Message;
import longrise.phone.com.bjjt_henan.Utils.icomet.message.MessageInputStream;

public class ICometClient
{

    public static class State
    {
        // status for a client just created
        public static final int STATE_NEW          = 0;
        // status for a prepared client
        public static final int STATE_READY        = 1;
        // status for a client which has connected to iComet server
        public static final int STATE_CONNCTED     = 2;
        // status for a client working with sending or receiving message
        public static final int STATE_COMET        = 3;
        // comet was stopped manually
        public static final int STATE_STOP         = 4;
        // stop the comet client
        public static final int STATE_STOP_PENDING = 5;
        // disconnect from iComet server, usually by error
        public static final int STATE_DISCONNECT   = 6;
    }

    // delay for reconnection, if times of reconnection is larger than 3, treat as 3
    // you can deal with reconnection with onReconnect() method. too much times of reconnection is not recommended
    private static final int[]  DELAY        = { 30, 120, 600 };

    // host for your iComet server
    //	private String host;
    //	// port of the server for iComet
    //	private String port;
    // the URL for connection to iComet server
    private String              finalUrl;
    // record the times of reconnection
    private int                 mReconnTimes = 0;
    // channel object got from your business server
    private Channel             mChannel;

    private static ICometClient mClient      = new ICometClient();
    private ICometCallback      mICometCallback;
    private IConnCallback       mIConnCallback;

    private HttpURLConnection   mConn;
    private MessageInputStream mInput;

    private ICometConf          mConf;

    // current status
    private int                 mStatus      = State.STATE_NEW;

    private ICometClient()
    {

    }

    /**
     * get the single Instance of ICometClient
     *
     * @return
     */
    public static ICometClient getInstance ()
    {
        if ( mClient == null )
        {
            mClient = new ICometClient();
        }
        return mClient;
    }

    /**
     * prepare for connection
     *
     * @param conf
     */
    public void prepare ( ICometConf conf )
    {
        if(null == conf){
            return;
        }
        if ( conf.channelAllocator == null )
        {
            conf.channelAllocator = new DefaultChannelAllocator();
        }
        mConf = conf;
        if ( mReconnTimes == 0 )
        {
            this.mChannel = conf.channelAllocator.allocate();
        }
        this.finalUrl = buildURL(conf.url);
        this.mICometCallback = conf.iCometCallback;
        this.mIConnCallback = conf.iConnCallback;
        this.mStatus = State.STATE_READY;
    }

    /**
     * connect to iComet server please call this method in a child thread
     */
    public void connect ()
    {
        if ( this.mStatus != State.STATE_READY )
        {
            return;
        }
        try
        {
            mConn = (HttpURLConnection) new URL(this.finalUrl).openConnection();
            mConn.setRequestMethod("GET");
            mConn.setConnectTimeout(3 * 60 * 1000);
            mConn.setReadTimeout(3 * 60 * 1000);
            mConn.setDoInput(true);
            mConn.connect();
            mInput = new MessageInputStream(mConn.getInputStream());

        }
        catch (Exception e)
        {
            if ( mConn != null )
            {
                mConn.disconnect();
            }
            if ( mIConnCallback != null )
            {
                mIConnCallback.onFail(e.getMessage());
            }
            reconnect();
            return;
        }

        this.mStatus = State.STATE_CONNCTED;

        if ( mIConnCallback != null )
        {
            if ( mReconnTimes == 0 )
            {
                mIConnCallback.onSuccess();
            }
            else
            {
                mIConnCallback.onReconnectSuccess(mReconnTimes);
                mReconnTimes = 0;
            }
        }

    }

    /**
     * start a new thread to deal with the data transfer
     */
    public void comet ()
    {
        if ( this.mStatus != State.STATE_CONNCTED )
        {
            return;
        }
        this.mStatus = State.STATE_COMET;
        new SubThread().start();

    }

    /**
     * close the connection to iComet server
     */
    public void stopComet ()
    {
        mStatus = State.STATE_STOP_PENDING;
    }

    /**
     * stop connecting to iComet server
     */
    public void stopConnect ()
    {
        if ( mConn != null )
        {
            mConn.disconnect();
            mConn = null;
        }
        mConf = null;
        mIConnCallback = null;
        mICometCallback = null;
    }

    /**
     * get current status of this client
     *
     * @return status
     * @see Status
     */
    public int currStatus ()
    {
        return mStatus;
    }

    /**
     * used to reconnect to the server when the connection lose or an error occur
     */
    private void reconnect ()
    {
        if ( mIConnCallback == null )
        {
            return;
        }
        mChannel.seq = 0;
        final Timer timer = new Timer();
        timer.schedule(new TimerTask()
        {

            public void run ()
            {
                mReconnTimes++;
                if (null != mIConnCallback && !mIConnCallback.onReconnect(mReconnTimes) )
                {
                    if ( mStatus != State.STATE_READY )
                    {
                        prepare(mConf);
                    }
                    connect();
                }
                timer.cancel();
            }
        }, DELAY[mReconnTimes > 2 ? 2 : mReconnTimes] * 1000);

    }

    /**
     * build the URL by method and args
     *
     *            argument
     * @return URL
     */
    private String buildURL ( String url )
    {
        StringBuilder sb = new StringBuilder();
        if ( !this.mConf.host.startsWith("http://") )
        {
            sb.append("http://");
        }
        sb.append(this.mConf.host);
        if ( !isEmpty(this.mConf.port) )
        {
            sb.append(":").append(this.mConf.port);
        }
        if ( !isEmpty(url) )
        {
            sb.append("/").append(url);
        }
        if ( mChannel == null )
        {
            return sb.toString();
        }

        sb.append("?");
        sb.append("cname=").append(mChannel.cname);
        sb.append("&").append("seq=").append(mChannel.seq);
        sb.append("&").append("token=").append(mChannel.token);
        return sb.toString();
    }

    /**
     * thread for retrieving data from iComet server
     *
     * @author keyleduo
     */
    private class SubThread extends Thread
    {
        public void run ()
        {
            super.run();

            if ( mICometCallback == null )
            {
                throw new IllegalArgumentException(
                        "There always should be an ICometCallback to deal with the coming data");
            }

            try
            {
                while (mStatus == ICometClient.State.STATE_COMET)
                {
                    // block here
                    Message msg = mInput.readMessage();

                    if ( msg != null )
                    {

                        mICometCallback.onMsgArrived(msg);

                        if ( msg.type.equals(Message.Type.TYPE_DATA) && msg.content != null && msg.content.length() > 0 )
                        {
                            mChannel.seq++;
                            mICometCallback.onDataMsgArrived(msg.content);
                        }
                        else if ( msg.type.equals(Message.Type.TYPE_NOOP) )
                        {

                        }
                        else
                        {
                            mICometCallback.onErrorMsgArrived(msg);
                        }

                    }
                    else
                    {
                    }
                }
            }
            catch (Exception e)
            {
                e.printStackTrace();
                if (null != mIConnCallback){
                    mIConnCallback.onDisconnect();
                }
                mStatus = ICometClient.State.STATE_DISCONNECT;
                reconnect();
                return;
            }

            mStatus = ICometClient.State.STATE_STOP;
            if ( mIConnCallback != null )
            {
                mIConnCallback.onStop();
            }
        }
    }

    /**
     * judge if the source is empty
     *
     * @param source
     * @return
     */
    public boolean isEmpty ( String source )
    {
        if ( source == null || source.length() < 1 )
        {
            return true;
        }
        return false;
    }

}
